ZF.add('dom-query', function(Z) {

    var D = Z.Dom,
        L = Z.Lang,
        S = Z.String,
        doc = document,
        ANY = '*',
        SPACE = ' ',
        R_ID = /^#[\w-]+$/,
        R_XPATH = /^(?:#([\w-]+))?\s*([\w-]+|\*)?\.?([\w-]+)?$/,
        GET_ELEMENT_BY_ID = 'getElementById',
        GET_ELEMENT_BY_CLASS = 'getElementsByClassName',
        GET_ELEMENT_BY_TAG = 'getElementsByTagName',
        QUERY_SELECTOR_ALL = 'querySelectorAll';


    
    // #id
    function getElementById(id, context) {
        context = context || doc;

        if(context.nodeType !== 9) {
            context = context.ownerDocument;
        }

        return context[GET_ELEMENT_BY_ID](id);
    }

    function getElementsByTagName(tag, context) {
        context = context || doc;
        return context[GET_ELEMENT_BY_TAG](tag);
    }


    var getElementsByClassName;

    if (!doc[GET_ELEMENT_BY_CLASS]) {

        // native querySelectorAll
        if (doc[QUERY_SELECTOR_ALL]) {

            getElementsByClassName = function(cls, tag, context) {
                return context[QUERY_SELECTOR_ALL]((tag || '') + '.' + cls);
            }

        } else {

            // implement by self
            getElementsByClassName = function(cls, tag, context) {
                var els = context[GET_ELEMENT_BY_TAG](tag || ANY),
                    ret = [],
                    i = 0,
                    j = 0,
                    n = els.length,
                    el,
                    t;

                cls = SPACE + cls + SPACE;

                for (; i < n; ++i) {
                    el = els[i];
                    t = el.className;
                    if (t && (SPACE + t + SPACE).indexOf(cls) > -1) {
                        ret[j++] = el;
                    }
                }

                return ret;
            }

        }

    } else {

        getElementsByClassName = function(cls, tag, context) {
            var els = context[GET_ELEMENT_BY_CLASS](cls),
                ret = els,
                i = 0,
                j = 0,
                n = els.length,
                el;

            if (tag && tag !== ANY) {
                ret = [];
                tag = tag.toUpperCase();
                for (; i < n; ++i) {
                    el = els[i];
                    if (el.tagName === tag) {
                        ret[j++] = el;
                    }
                }
            }
            return ret;
        }

    }



    /**
     * Retrieves an Array of HTMLElement based on the given CSS selector.
     * @param {string} selector
     * @param {string|HTMLElement} context an #id string or a HTMLElement used as context
     * @return {Array} The array of found HTMLElement
     */
    function query(selector, context) {
        /*var els = [], el = this.id(selector);
        if (el) {
            els.push(el);
        }
        return els;*/

        // Ref: http://ejohn.org/blog/selectors-that-people-actually-use/
        // 考虑 2/8 原则，仅支持以下选择器：
        // #id
        // tag
        // .cls
        // #id tag
        // #id .cls
        // tag.cls
        // #id tag.cls
        // 注 1：REG_QUERY 还会匹配 #id.cls
        // 注 2：tag 可以为 * 字符
        // 返回值为数组

        var ret = [], el, match, id, tag, cls;
        
        context = filterContext(context);

        if (L.isString(selector)) {

            selector = S.trim(selector);

            // #id
            if (R_ID.test(selector)) {
                
                el = getElementById(selector.slice(1), context);
                
                if (el) {
                    ret = [el];
                }

            } else if ((match = R_XPATH.exec(selector))) {
                
                id = match[1];
                tag = match[2];
                cls = match[3];

                if ((context = (id ? getElementById(id, context) : context))) {
                    // #id .cls | #id tag.cls | .cls | tag.cls
                    if (cls) {
                        // exclude #id.cls
                        if (!id || selector.indexOf(SPACE) !== -1) {
                            ret = getElementsByClassName(cls, tag, context);
                        } else { // #id.cls
                            t = getElementById(id, context);
                            if(t && D.hasClass(t, cls)) {
                                ret = [t];
                            }
                        }
                    } else if (tag) { // #id tag | tag
                        ret = getElementsByTagName(tag, context);
                    }
                }

            } else {
                Z.error('unknown selector: ' + msg);
            }

        } else if (selector && (L.isArray(selector) || isNodeList(selector))) {
            ret = selector;
        } else if (selector) {
            ret = [selector];
        }

        if (isNodeList(ret)) {
            ret = L.toArray(ret);
        }

        if (!ret.each) {
            ret.each = function(fn, context) {
                L.each(this, fn, context)
            };
        }

        return ret;
    }


    // Ref: http://lifesinger.github.com/lab/2010/nodelist.html
    function isNodeList(o) {
        // 注1：ie 下，有 window.item, typeof node.item 在 ie 不同版本下，返回值不同
        // 注2：select 等元素也有 item, 要用 !node.nodeType 排除掉
        // 注3：通过 namedItem 来判断不可靠
        // 注4：getElementsByTagName 和 querySelectorAll 返回的集合不同
        // 注5: 考虑 iframe.contentWindow
        return o && !o.nodeType && o.item && !o.setTimeout;
    }

    // 调整 context 为合理值
    function filterContext(context) {
        if (context === undefined) { // 1. context 为 undefined 是最常见的情况，优先考虑
            context = doc;
        } else if (L.isString(context) && R_ID.test(context)) { // 2. context 的第二使用场景是传入 #id
            context = getElementById(context.slice(1)); // 注：#id 可能无效，这时获取的 context 为 null
        } else if (context && !D.isType(context, 1) && !D.isType(context, 9)) { // 3. context 还可以传入 HTMLElement, 此时无需处理
            context = null;
        }
        return context;
    }


    Z.extend(D, {

        id : getElementById,
        
        get : function(selector, context) {
            return query(selector, context)[0] || null;
        },

        query : query,
        
        getByClass : getElementsByClassName

    });

}, { requires : ['dom', 'dom-class'] });
